Default to bitrouter/auto, and read the catalog off the real wire - #6
Merged
Merged
Conversation
**The auto route.** `bitrouter/auto` hands model choice back to BitRouter: the
request carries `auto` as its model and the gateway's policy picks per request.
It leads every catalog the plugin produces, and the `config` hook names it as
`model` and `small_model` — so a fresh opencode.json carrying nothing but
`"plugin": ["@bitrouter/opencode"]` is a complete installation.
`??=` is the whole of the courtesy: a model the user wrote themselves, or
another plugin set first, is left exactly as it stands. Routing the small-model
errands through `auto` is what the auto route is for — BitRouter's own ladder
is a better judge of "cheap enough for this" than a hardcoded second model id.
It is the default, not the only option: the rest of the catalog is registered
behind it. A served `auto` entry always wins over the synthesized one, so the
real capacities arrive with no release here.
Before authentication on cloud the provider is now seeded with the auto route
alone rather than a `kimi-k2.5` placeholder — without at least one model the
provider would not be selectable and `/connect` would be unreachable.
**The discovery field mapping.** Neither data plane sends `context_window`, a
flat `cost` object, or boolean `reasoning`/`tool_call` fields — which is what
this plugin was reading. Cloud sends `max_input_tokens`, nested per-million
`pricing`, and `capabilities` tokens; the local daemon sends `{id, object,
providers}` and nothing else. Every cloud model was therefore shown at the 128K
default and priced at zero, a 1M-context model included.
Absent capability tokens read as "the plane did not say" rather than "no": the
local daemon never sends them, and a coding agent is unusable against a model
it believes cannot call tools.
test/wire.test.ts pins the mapping against bodies captured verbatim from both
planes, so a field rename upstream fails a test rather than silently defaulting
a model.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`tsconfig.test.json` is stricter than the source project and is its own CI step. `modalities` and `limit` are optional on opencode's config-model type, so reading through them needs `?.` — the assertion is unchanged either way, since an undefined left-hand side still fails the comparison. The plugin.test.ts ProviderBlock helper gains the `limit` it now asserts on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BitRouter reserves the whole `bitrouter/` namespace for itself (`RESERVED_NAMESPACE` in `crates/bitrouter-sdk/src/config/presets.rs`) and `bitrouter/auto` is the public slug for policy-driven routing (`AUTO_SLUG`). The vendor segment names the router being addressed, not the token destination. A bare `auto` is not that id: the gateway answers 404 `no active provider declares model 'auto'`, because nothing resolves it before the provider lookup. Verified end to end against a daemon carrying the reserved-namespace build: `bitrouter/auto` completes and the response echoes the public slug, while the request is fulfilled by whichever tier the bound policy picks. Two claims went with it. The namespace is resolved before any provider lookup and BitRouter's registry validator refuses catalog models under `bitrouter/`, so `GET /v1/models` will never list this id — the entry has to come from here, and the "once the catalog lists it" framing was wrong. The synthesized-entry check is kept anyway: it costs nothing and stops the placeholder shadowing a served entry if one ever appears. The id still resolves only where a preset named `auto` is bound to a routing policy; without one the gateway answers 400 naming `bitrouter optimize setup`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
1.
bitrouter/autoleads every catalog, and is the default model. The request carriesbitrouter/autoas its model and BitRouter's routing policy picks per request. Theconfighook names it as bothmodelandsmall_model, so a freshopencode.jsoncarrying nothing but"plugin": ["@bitrouter/opencode"]is a complete installation.??=is the whole of the courtesy: amodelthe user wrote themselves, or another plugin set first, is left exactly as it stands. Routing the small-model errands throughautois what the auto route is for — BitRouter's own ladder is a better judge of "cheap enough for this" than a hardcoded second model id.bitrouter/autois the default, not the only option — the rest of the catalog is registered behind it.Before authentication on cloud the provider is now seeded with the auto route alone rather than a
kimi-k2.5placeholder; without at least one model the provider would not be selectable and/connectwould be unreachable.2. The discovery field mapping was wrong against both data planes.
Neither plane sends
context_window, a flatcostobject, or booleanreasoning/tool_callfields — which is what this plugin was reading:context_windowmax_input_tokenscost.*pricing(nested, per-million)reasoning/tool_callcapabilitiestokensproviders: string[]{ total_online }Verified against the live endpoints: all 50 cloud models now carry true context windows (1M for Fable 5, 200K for Opus 4.6, Haiku's 8192 output cap) where previously every one fell back to the default.
Absent capability tokens read as "the plane did not say" rather than "no" — the local daemon never sends them, and a coding agent is unusable against a model it believes cannot call tools.
test/wire.test.tspins the mapping against bodies captured verbatim from both planes, so a field rename upstream fails a test rather than silently defaulting a model.Placeholder capacities
Until BitRouter's catalog lists
auto, the entry is synthesized at 128K context / 16K output. Those are deliberately the floor rather than the ceiling:automay land on any model in the ladder, and the two wrong answers do not cost the same — under-claiming compacts a session early, over-claiming fails a request outright, mid-turn. A servedautoentry always wins, so real capacities arrive with no release here.Verified end to end ✅
Against a live BitRouter daemon carrying the reserved-namespace build, with an
autopreset bound to a routing policy:bitrouter/autoofferedbitrouter/autoThe model id is⚠️ (corrected)
bitrouter/autoAn earlier revision of this branch used a bare
auto. That was wrong: BitRouter reserves the wholebitrouter/namespace for itself (RESERVED_NAMESPACE) andbitrouter/autois the public slug (AUTO_SLUG) — the vendor segment names the router being addressed, not the token destination. A bareautogets404 no active provider declares model 'auto'.Two consequences, both now reflected in the code and docs:
GET /v1/modelswill never list this id. The namespace resolves before any provider lookup, and the registry validator refuses catalog models underbitrouter/— so the entry has to be synthesized here. The "once the catalog lists it" framing in the first revision was wrong.autobound to a routing policy. Without one the gateway answers400namingbitrouter optimize setup. Worth knowing before this ships to users, since a fresh install has no such preset.The reserved-namespace support is not in the released binary — homebrew
1.0.0-alpha.26returns 404 for it;alpha.27dev builds resolve it. That, not this branch, is the real release gate.Tests
45 → 63, including the new
wire.test.tsfixtures.🤖 Generated with Claude Code